/*-------------------------------------------------------------------------
 *
 * xlogreader.c
 *		通用 XLog 读取设施
 *
 * Portions Copyright (c) 2013-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		src/backend/access/transam/xlogreader.c
 *
 * NOTES
 *		有关此设施的更多注释，请参阅 xlogreader.h。
 *
 *		此文件既可以作为前端代码，也可以作为后端代码进行编译，
 *		因此它不能使用 ereport、服务器定义的静态变量等。
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>
#ifdef USE_LZ4
#include <lz4.h>
#endif
#ifdef USE_ZSTD
#include <zstd.h>
#endif

#include "access/transam.h"
#include "access/xlog_internal.h"
#include "access/xlogreader.h"
#include "access/xlogrecord.h"
#include "catalog/pg_control.h"
#include "common/pg_lzcompress.h"
#include "replication/origin.h"

#ifndef FRONTEND
#include "miscadmin.h"
#include "pgstat.h"
#include "utils/memutils.h"
#else
#include "common/logging.h"
#endif

static void fc_report_invalid_record(XLogReaderState *fc_state, const char *fc_fmt,...)
			pg_attribute_printf(2, 3);
static void fc_allocate_recordbuf(XLogReaderState *fc_state, uint32 fc_reclength);
static int	fc_ReadPageInternal(XLogReaderState *fc_state, XLogRecPtr fc_pageptr,
							 int fc_reqLen);
static void fc_XLogReaderInvalReadState(XLogReaderState *fc_state);
static XLogPageReadResult fc_XLogDecodeNextRecord(XLogReaderState *fc_state, bool fc_non_blocking);
static bool fc_ValidXLogRecordHeader(XLogReaderState *fc_state, XLogRecPtr fc_RecPtr,
								  XLogRecPtr fc_PrevRecPtr, XLogRecord *fc_record, bool fc_randAccess);
static bool fc_ValidXLogRecord(XLogReaderState *fc_state, XLogRecord *fc_record,
							XLogRecPtr fc_recptr);
static void fc_ResetDecoder(XLogReaderState *fc_state);
static void fc_WALOpenSegmentInit(WALOpenSegment *fc_seg, WALSegmentContext *fc_segcxt,
							   int fc_segsize, const char *fc_waldir);

/* 为错误消息分配的缓冲区的大小。 */
#define MAX_ERRORMSG_LEN 1000

/*
 * 默认大小；足够大，以至于 XLogReader 的典型用户不会经常需要
 * 使用“超大”内存分配代码路径。
 */
#define DEFAULT_DECODE_BUFFER_SIZE (64 * 1024)

/*
 * 在 state->errormsg_buf 中构造一个字符串，解释当前读取的记录
 * 存在什么问题。
 */
static void fc_report_invalid_record(XLogReaderState *fc_state, const char *fc_fmt,...)
{
	va_list		fc_args;

	fc_fmt = _(fc_fmt);

	va_start(fc_args, fc_fmt);
	vsnprintf(fc_state->errormsg_buf, MAX_ERRORMSG_LEN, fc_fmt, fc_args);
	va_end(fc_args);

	fc_state->errormsg_deferred = true;
}

/*
 * 设置解码缓冲区的大小。  还可以传入指向调用者提供的内存
 * 区域的指针，此时非超大记录将在那里解码。
 */
void XLogReaderSetDecodeBuffer(XLogReaderState *fc_state, void *fc_buffer, size_t fc_size)
{
	Assert(fc_state->decode_buffer == NULL);

	fc_state->decode_buffer = fc_buffer;
	fc_state->decode_buffer_size = fc_size;
	fc_state->decode_buffer_tail = fc_buffer;
	fc_state->decode_buffer_head = fc_buffer;
}

/*
 * 分配并初始化一个新的 XLogReader。
 *
 * 如果无法分配 xlogreader，则返回 NULL。
 */
XLogReaderState *
XLogReaderAllocate(int fc_wal_segment_size, const char *fc_waldir,
				   XLogReaderRoutine *fc_routine, void *fc_private_data)
{
	XLogReaderState *fc_state;

	fc_state = (XLogReaderState *)
		palloc_extended(sizeof(XLogReaderState),
						MCXT_ALLOC_NO_OOM | MCXT_ALLOC_ZERO);
	if (!fc_state)
		return NULL;

	/* 初始化调用者提供的支持函数 */
	fc_state->routine = *fc_routine;

	/*
	 * 永久分配 readBuf。我们这样做，而不是仅仅
	 * 创建一个静态数组，原因有两个：(1) 在大多数后端实例中没有必要浪费
	 * 存储；(2) 静态 char 数组不能保证具有任何特定的对齐方式，而
	 * palloc_extended() 将提供 MAXALIGN 的存储。
	 */
	fc_state->readBuf = (char *) palloc_extended(XLOG_BLCKSZ,
											  MCXT_ALLOC_NO_OOM);
	if (!fc_state->readBuf)
	{
		pfree(fc_state);
		return NULL;
	}

	/* 初始化段信息。 */
	fc_WALOpenSegmentInit(&fc_state->seg, &fc_state->segcxt, fc_wal_segment_size,
					   fc_waldir);

	/* system_identifier 在上面初始化为零 */
	fc_state->private_data = fc_private_data;
	/* ReadRecPtr、EndRecPtr 和 readLen 在上面初始化为零 */
	fc_state->errormsg_buf = palloc_extended(MAX_ERRORMSG_LEN + 1,
										  MCXT_ALLOC_NO_OOM);
	if (!fc_state->errormsg_buf)
	{
		pfree(fc_state->readBuf);
		pfree(fc_state);
		return NULL;
	}
	fc_state->errormsg_buf[0] = '\0';

	/*
	 * 分配一个初始的 readRecordBuf，大小最小，必要时可以
	 * 扩大。
	 */
	fc_allocate_recordbuf(fc_state, 0);
	return fc_state;
}

void XLogReaderFree(XLogReaderState *fc_state)
{
	if (fc_state->seg.ws_file != -1)
		fc_state->routine.segment_close(fc_state);

	if (fc_state->decode_buffer && fc_state->free_decode_buffer)
		pfree(fc_state->decode_buffer);

	pfree(fc_state->errormsg_buf);
	if (fc_state->readRecordBuf)
		pfree(fc_state->readRecordBuf);
	pfree(fc_state->readBuf);
	pfree(fc_state);
}

/*
 * 分配 readRecordBuf，以适应至少给定长度的记录。
 *
 * readRecordBufSize 被设置为新的缓冲区大小。
 *
 * 为了避免无用的小增加，将其大小四舍五入为
 * XLOG_BLCKSZ 的倍数，确保起始时至少为 5*Max(BLCKSZ, XLOG_BLCKSZ)。
 * （这对于所有“正常”记录足够，但非常大的提交或
 * 中止记录可能需要更多空间。）
 *
 * 注意：在记录的头部完全验证之前，这个例程*绝不能*用于 xl_tot_len。
 */
static void fc_allocate_recordbuf(XLogReaderState *fc_state, uint32 fc_reclength)
{
	uint32		fc_newSize = fc_reclength;

	fc_newSize += XLOG_BLCKSZ - (fc_newSize % XLOG_BLCKSZ);
	fc_newSize = Max(fc_newSize, 5 * Max(BLCKSZ, XLOG_BLCKSZ));

	if (fc_state->readRecordBuf)
		pfree(fc_state->readRecordBuf);
	fc_state->readRecordBuf = (char *) palloc(fc_newSize);
	fc_state->readRecordBufSize = fc_newSize;
}

/*
 * 初始化传递的段结构。
 */
static void fc_WALOpenSegmentInit(WALOpenSegment *fc_seg, WALSegmentContext *fc_segcxt,
				   int fc_segsize, const char *fc_waldir)
{
	fc_seg->ws_file = -1;
	fc_seg->ws_segno = 0;
	fc_seg->ws_tli = 0;

	fc_segcxt->ws_segsize = fc_segsize;
	if (fc_waldir)
		snprintf(fc_segcxt->ws_dir, MAXPGPATH, "%s", fc_waldir);
}

/*
 * 在 'RecPtr' 开始读取 WAL。
 *
 * 'RecPtr' 应指向有效 WAL 记录的开始。 如果指向
 * 页面开始也是可以的，前提是页面头后面紧接着有新记录，即
 * 不是继续记录。
 *
 * 这还没有尝试读取 WAL，因此不能失败。
 * 如果起始地址不正确，第一次调用 XLogReadRecord()
 * 将会出错。
 */
void XLogBeginRead(XLogReaderState *fc_state, XLogRecPtr fc_RecPtr)
{
	Assert(!XLogRecPtrIsInvalid(fc_RecPtr));

	fc_ResetDecoder(fc_state);

	/* 从传入的记录指针开始。 */
	fc_state->EndRecPtr = fc_RecPtr;
	fc_state->NextRecPtr = fc_RecPtr;
	fc_state->ReadRecPtr = InvalidXLogRecPtr;
	fc_state->DecodeRecPtr = InvalidXLogRecPtr;
}

/*
 * 释放 XLogNextRecord() 返回的最后一个记录（如果有），以
 * 释放空间。返回记录结束后的 LSN。
 */
XLogRecPtr XLogReleasePreviousRecord(XLogReaderState *fc_state)
{
	DecodedXLogRecord *fc_record;
	XLogRecPtr		fc_next_lsn;

	if (!fc_state->record)
		return InvalidXLogRecPtr;

	/*
	 * 从解码记录队列中移除它。它必须是解码队列头中
	 * 解码的最旧项。
	 */
	fc_record = fc_state->record;
	fc_next_lsn = fc_record->next_lsn;
	Assert(fc_record == fc_state->decode_queue_head);
	fc_state->record = NULL;
	fc_state->decode_queue_head = fc_record->next;

	/* 它也可能是解码队列尾中解码的最新项。 */
	if (fc_state->decode_queue_tail == fc_record)
		fc_state->decode_queue_tail = NULL;

	/* 释放空间。 */
	if (unlikely(fc_record->oversized))
	{
		/* 它不在解码缓冲区中，因此释放空间。 */
		pfree(fc_record);
	}
	else
	{
		/* 它必须是解码缓冲区中的头（最旧）记录。 */
		Assert(fc_state->decode_buffer_head == (char *) fc_record);

		/*
		 * 我们需要更新头指针以指向解码缓冲区中的下一个记录（如果有），并小心跳过过大的记录（它们不在解码缓冲区中）。
		 */
		fc_record = fc_record->next;
		while (unlikely(fc_record && fc_record->oversized))
			fc_record = fc_record->next;

		if (fc_record)
		{
			/* 调整头指针以释放到下一个记录的空间。 */
			fc_state->decode_buffer_head = (char *) fc_record;
		}
		else
		{
			/*
			 * 否则我们可以将头指针和尾指针重置到缓冲区的起始位置，因为我们是空的。这意味着如果我们不进行任何预取，就会不断覆盖同一块内存。
			 */
			fc_state->decode_buffer_head = fc_state->decode_buffer;
			fc_state->decode_buffer_tail = fc_state->decode_buffer;
		}
	}

	return fc_next_lsn;
}

/*
 * 尝试读取一个XLOG记录。
 *
 * 必须在第一次调用XLogNextRecord()之前调用XLogBeginRead()或XLogFindNextRecord()和XLogReadAhead()。此函数返回通过XLogReadAhead()放入内部队列的记录和错误。
 *
 * 成功时，返回一个记录。
 *
 * 返回的记录（或*errormsg）指向一个内部缓冲区，该缓冲区在下一次调用XLogNextRecord之前有效。
 */
DecodedXLogRecord *
XLogNextRecord(XLogReaderState *fc_state, char **fc_errormsg)
{
	/* 释放XLogNextRecord()返回的最后一条记录。 */
	XLogReleasePreviousRecord(fc_state);

	if (fc_state->decode_queue_head == NULL)
	{
		*fc_errormsg = NULL;
		if (fc_state->errormsg_deferred)
		{
			if (fc_state->errormsg_buf[0] != '\0')
				*fc_errormsg = fc_state->errormsg_buf;
			fc_state->errormsg_deferred = false;
		}

		/*
		 * state->EndRecPtr应该在最后一次调用XLogBeginRead()或XLogNextRecord()时设置，它是错误的位置。
		 */
		Assert(!XLogRecPtrIsInvalid(fc_state->EndRecPtr));

		return NULL;
	}

	/*
	 * 将此记录记录为最近返回的记录，以便我们下次释放它。这也使它暴露于传统的XLogRecXXX(xlogreader)宏，这些宏因历史原因与解码器而不是与记录一起工作。
	 */
	fc_state->record = fc_state->decode_queue_head;

	/*
	 * 更新指向此记录的开始和结束一字节之后的指针，再次为了历史代码的利益，这些代码期望解码器跟踪此内容，而不是访问记录本身的这些字段。
	 */
	fc_state->ReadRecPtr = fc_state->record->lsn;
	fc_state->EndRecPtr = fc_state->record->next_lsn;

	*fc_errormsg = NULL;

	return fc_state->record;
}

/*
 * 尝试读取一个XLOG记录。
 *
 * 必须在第一次调用XLogReadRecord()之前调用XLogBeginRead()或XLogFindNextRecord()。
 *
 * 如果page_read回调无法读取请求的数据，则返回NULL。回调期望报告错误；errormsg设置为NULL。
 *
 * 如果因为其他原因读取失败，也返回NULL，并且*errormsg设置为包含失败细节的字符串。
 *
 * 返回的指针（或*errormsg）指向一个内部缓冲区，该缓冲区在下一次调用XLogReadRecord之前有效。
 */
XLogRecord *
XLogReadRecord(XLogReaderState *fc_state, char **fc_errormsg)
{
	DecodedXLogRecord *fc_decoded;

	/*
	 * 释放最后返回的记录（如果有的话）。我们需要这样做，以便能够准确检查是否为空的解码队列。
	 */
	XLogReleasePreviousRecord(fc_state);

	/*
	 * 在阻塞模式下调用XLogReadAhead()以确保队列中有内容，尽管我们不使用该结果。
	 */
	if (!XLogReaderHasQueuedRecordOrError(fc_state))
		XLogReadAhead(fc_state, false /* 非阻塞 */ );

	/* 消费头记录或错误。 */
	fc_decoded = XLogNextRecord(fc_state, fc_errormsg);
	if (fc_decoded)
	{
		/*
		 * 此函数返回指向记录头的指针，而不是实际解码的记录。调用者将通过XLogRecGetXXX()宏访问解码记录，该宏可以到达解码记录作为xlogreader->record。
		 */
		Assert(fc_state->record == fc_decoded);
		return &fc_decoded->header;
	}

	return NULL;
}

/*
 * 为解码记录分配空间。返回对象中唯一被初始化的成员是'oversized'标志，指示解码记录不能适应解码缓冲区，最终必须显式释放。
 *
 * 调用者有责任在成功解码记录到此空间后，使用实际大小调整decode_buffer_tail。这样，如果解码失败，则没有需要撤销的内容，除非设置了'oversized'标志并且必须调用pfree()。
 *
 * 如果解码缓冲区没有空间并且allow_oversized为false，或者如果对超大缓冲区的内存分配失败，则返回NULL。
 */
static DecodedXLogRecord *
fc_XLogReadRecordAlloc(XLogReaderState *fc_state, size_t fc_xl_tot_len, bool fc_allow_oversized)
{
	size_t		fc_required_space = DecodeXLogRecordRequiredSpace(fc_xl_tot_len);
	DecodedXLogRecord *fc_decoded = NULL;

	/* 如果我们还没有分配一个，则分配一个循环解码缓冲区。 */
	if (unlikely(fc_state->decode_buffer == NULL))
	{
		if (fc_state->decode_buffer_size == 0)
			fc_state->decode_buffer_size = DEFAULT_DECODE_BUFFER_SIZE;
		fc_state->decode_buffer = palloc(fc_state->decode_buffer_size);
		fc_state->decode_buffer_head = fc_state->decode_buffer;
		fc_state->decode_buffer_tail = fc_state->decode_buffer;
		fc_state->free_decode_buffer = true;
	}

	/* 尝试在循环解码缓冲区中分配空间。 */
	if (fc_state->decode_buffer_tail >= fc_state->decode_buffer_head)
	{
		/* 空，或者尾部在头部的右侧。 */
		if (fc_required_space <=
			fc_state->decode_buffer_size -
			(fc_state->decode_buffer_tail - fc_state->decode_buffer))
		{
			/*-
			 * 尾部和结束之间有空间。
			 *
			 * +-----+--------------------+-----+
			 * |     |////////////////////|这里!|
			 * +-----+--------------------+-----+
			 *       ^                    ^
			 *       |                    |
			 *       h                    t
			 */
			fc_decoded = (DecodedXLogRecord *) fc_state->decode_buffer_tail;
			fc_decoded->oversized = false;
			return fc_decoded;
		}
		else if (fc_required_space <
				 fc_state->decode_buffer_head - fc_state->decode_buffer)
		{
			/*-
			 * 开始和头部之间有空间。
			 *
			 * +-----+--------------------+-----+
			 * |这里!|////////////////////|     |
			 * +-----+--------------------+-----+
			 *       ^                    ^
			 *       |                    |
			 *       h                    t
			 */
			fc_decoded = (DecodedXLogRecord *) fc_state->decode_buffer;
			fc_decoded->oversized = false;
			return fc_decoded;
		}
	}
	else
	{
		/* 尾部在头部的左侧。 */
		if (fc_required_space <
			fc_state->decode_buffer_head - fc_state->decode_buffer_tail)
		{
			/*-
			 * 尾部和头部之间有空间。
			 *
			 * +-----+--------------------+-----+
			 * |/////|这里!               |/////|
			 * +-----+--------------------+-----+
			 *       ^                    ^
			 *       |                    |
			 *       t                    h
			 */
			fc_decoded = (DecodedXLogRecord *) fc_state->decode_buffer_tail;
			fc_decoded->oversized = false;
			return fc_decoded;
		}
	}

	/* 解码缓冲区空间不足。我们被允许分配吗？ */
	if (fc_allow_oversized)
	{
		fc_decoded = palloc(fc_required_space);
		fc_decoded->oversized = true;
		return fc_decoded;
	}

	return NULL;
}

static XLogPageReadResult
fc_XLogDecodeNextRecord(XLogReaderState *fc_state, bool fc_nonblocking)
{
	XLogRecPtr	fc_RecPtr;
	XLogRecord *fc_record;
	XLogRecPtr	fc_targetPagePtr;
	bool		fc_randAccess;
	uint32		fc_len,
				fc_total_len;
	uint32		fc_targetRecOff;
	uint32		fc_pageHeaderSize;
	bool		fc_assembled;
	bool		fc_gotheader;
	int			fc_readOff;
	DecodedXLogRecord *fc_decoded;
	char	   *fc_errormsg;		/* 未使用 */

	/*
	 * randAccess指示是否验证我们正在读取的记录的前一条记录指针。只有在顺序读取时才会这样做，这是我们最初的假设。
	 */
	fc_randAccess = false;

	/* 重置错误状态 */
	fc_state->errormsg_buf[0] = '\0';
	fc_decoded = NULL;

	fc_state->abortedRecPtr = InvalidXLogRecPtr;
	fc_state->missingContrecPtr = InvalidXLogRecPtr;

	fc_RecPtr = fc_state->NextRecPtr;

	if (fc_state->DecodeRecPtr != InvalidXLogRecPtr)
	{
		/* 读取我们刚刚读取的记录之后的记录 */

		/*
		 * NextRecPtr指向前一个WAL记录的结束+1。如果我们处于页面边界，则当前页面无法容纳更多记录。我们必须跳过页面头，但在读取页面之前不能这样做，因为头部大小是可变的。
		 */
	}
	else
	{
		/*
		 * 调用者提供了一个起始位置。
		 *
		 * 在这种情况下，NextRecPtr应该已经指向有效的记录起始位置。
		 */
		Assert(XRecOffIsValid(fc_RecPtr));
		fc_randAccess = true;
	}

restart:
	fc_state->nonblocking = fc_nonblocking;
	fc_state->currRecPtr = fc_RecPtr;
	fc_assembled = false;

	fc_targetPagePtr = fc_RecPtr - (fc_RecPtr % XLOG_BLCKSZ);
	fc_targetRecOff = fc_RecPtr % XLOG_BLCKSZ;

	/*
	 * 将包含记录的页面读取到state->readBuf中。请求足够的字节以覆盖整个记录头，或者至少覆盖同一页面上适合的部分。
	 */
	fc_readOff = fc_ReadPageInternal(fc_state, fc_targetPagePtr,
							   Min(fc_targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
	if (fc_readOff == XLREAD_WOULDBLOCK)
		return XLREAD_WOULDBLOCK;
	else if (fc_readOff < 0)
		goto err;

	/*
	 * ReadPageInternal始终至少返回页面头，因此我们现在可以检查它。
	 */
	fc_pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) fc_state->readBuf);
	if (fc_targetRecOff == 0)
	{
		/*
		 * 在页面开始时，因此跳过页面头。
		 */
		fc_RecPtr += fc_pageHeaderSize;
		fc_targetRecOff = fc_pageHeaderSize;
	}
	else if (fc_targetRecOff < fc_pageHeaderSize)
	{
		fc_report_invalid_record(fc_state, "invalid record offset at %X/%X",
							  LSN_FORMAT_ARGS(fc_RecPtr));
		goto err;
	}

	if ((((XLogPageHeader) fc_state->readBuf)->xlp_info & XLP_FIRST_IS_CONTRECORD) &&
		fc_targetRecOff == fc_pageHeaderSize)
	{
		fc_report_invalid_record(fc_state, "contrecord is requested by %X/%X",
							  LSN_FORMAT_ARGS(fc_RecPtr));
		goto err;
	}

	/* ReadPageInternal has verified the page header */
	Assert(fc_pageHeaderSize <= fc_readOff);

	/*
	 * 读取记录长度。
	 *
	 * 注意：尽管我们在这里使用了一个 XLogRecord 指针，但整个记录
	 * 头可能无法适合这个页面。xl_tot_len 是结构体的第一个字段，
	 * 所以它必须在这个页面上（记录使用了 MAXALIGN），但在确认我们
	 * 获取了整个头之前不能访问其他字段。
	 */
	fc_record = (XLogRecord *) (fc_state->readBuf + fc_RecPtr % XLOG_BLCKSZ);
	fc_total_len = fc_record->xl_tot_len;

	/*
	 * 如果整个记录头在这个页面上，立即验证它。
	 * 否则，只对 xl_tot_len 进行基本的合理性检查，并在从
	 * 下一个页面读取后验证其余的头。这里需要进行 xl_tot_len
	 * 检查，以确保我们进入下面的“需要重新组装记录”代码路径；
	 * 否则，我们可能根本无法应用 ValidXLogRecordHeader。
	 */
	if (fc_targetRecOff <= XLOG_BLCKSZ - SizeOfXLogRecord)
	{
		if (!fc_ValidXLogRecordHeader(fc_state, fc_RecPtr, fc_state->DecodeRecPtr, fc_record,
								   fc_randAccess))
			goto err;
		fc_gotheader = true;
	}
	else
	{
		/* 如果页面太小，可能没有下一个页面。 */
		if (fc_total_len < SizeOfXLogRecord)
		{
			fc_report_invalid_record(fc_state,
								  "invalid record length at %X/%X: wanted %u, got %u",
								  LSN_FORMAT_ARGS(fc_RecPtr),
								  (uint32) SizeOfXLogRecord, fc_total_len);
			goto err;
		}
		/* 一旦我们有下一个页面，就会验证头。 */
		fc_gotheader = false;
	}

	/*
	 * 尝试寻找空间来解码这个记录，如果可以不调用 palloc。
	 * 如果不行，我们将在下面重新尝试，在确认 total_len
	 * 不是来自回收 WAL 页面的一些垃圾字节后。
	 */
	fc_decoded = fc_XLogReadRecordAlloc(fc_state,
								  fc_total_len,
								  false /* allow_oversized */ );
	if (fc_decoded == NULL && fc_nonblocking)
	{
		/*
		 * 循环解码缓冲区没有空间，并且调用者只是向前读取。
		 * 调用者应该消费现有记录以腾出空间。
		 */
		return XLREAD_WOULDBLOCK;
	}

	fc_len = XLOG_BLCKSZ - fc_RecPtr % XLOG_BLCKSZ;
	if (fc_total_len > fc_len)
	{
		/* 需要重新组装记录 */
		char	   *fc_contdata;
		XLogPageHeader fc_pageHeader;
		char	   *fc_buffer;
		uint32		fc_gotlen;

		fc_assembled = true;

		/*
		 * 我们总是为几个页面保留空间，足以验证跨边界的记录头。
		 */
		Assert(fc_state->readRecordBufSize >= XLOG_BLCKSZ * 2);
		Assert(fc_state->readRecordBufSize >= fc_len);

		/* 从第一页复制记录的第一个片段。 */
		memcpy(fc_state->readRecordBuf,
			   fc_state->readBuf + fc_RecPtr % XLOG_BLCKSZ, fc_len);
		fc_buffer = fc_state->readRecordBuf + fc_len;
		fc_gotlen = fc_len;

		do
		{
			/* 计算指向下一个页面开始的指针 */
			fc_targetPagePtr += XLOG_BLCKSZ;

			/* 等待下一个页面变得可用 */
			fc_readOff = fc_ReadPageInternal(fc_state, fc_targetPagePtr,
									   Min(fc_total_len - fc_gotlen + SizeOfXLogShortPHD,
										   XLOG_BLCKSZ));

			if (fc_readOff == XLREAD_WOULDBLOCK)
				return XLREAD_WOULDBLOCK;
			else if (fc_readOff < 0)
				goto err;

			Assert(SizeOfXLogShortPHD <= fc_readOff);

			fc_pageHeader = (XLogPageHeader) fc_state->readBuf;

			/*
			 * 如果我们期望一个续集记录却得到了“覆盖对立记录”
			 * 标志，这意味着续集记录被不同的记录覆盖了。
			 * 通过假设读取地址是我们发现这个标志的地方，重新开始读取；
			 * 但要跟踪我们正在读取的记录的 LSN，以便后续验证。
			 */
			if (fc_pageHeader->xlp_info & XLP_FIRST_IS_OVERWRITE_CONTRECORD)
			{
				fc_state->overwrittenRecPtr = fc_RecPtr;
				fc_RecPtr = fc_targetPagePtr;
				goto restart;
			}

			/* 检查下一个页面的续集是否看起来有效 */
			if (!(fc_pageHeader->xlp_info & XLP_FIRST_IS_CONTRECORD))
			{
				fc_report_invalid_record(fc_state,
									  "there is no contrecord flag at %X/%X",
									  LSN_FORMAT_ARGS(fc_RecPtr));
				goto err;
			}

			/*
			 * 交叉检查 xlp_rem_len 与我们预期的剩余记录长度是否一致。
			 */
			if (fc_pageHeader->xlp_rem_len == 0 ||
				fc_total_len != (fc_pageHeader->xlp_rem_len + fc_gotlen))
			{
				fc_report_invalid_record(fc_state,
									  "invalid contrecord length %u (expected %lld) at %X/%X",
									  fc_pageHeader->xlp_rem_len,
									  ((long long) fc_total_len) - fc_gotlen,
									  LSN_FORMAT_ARGS(fc_RecPtr));
				goto err;
			}

			/* 将来自此页面的续集附加到缓冲区 */
			fc_pageHeaderSize = XLogPageHeaderSize(fc_pageHeader);

			if (fc_readOff < fc_pageHeaderSize)
				fc_readOff = fc_ReadPageInternal(fc_state, fc_targetPagePtr,
										   fc_pageHeaderSize);

			Assert(fc_pageHeaderSize <= fc_readOff);

			fc_contdata = (char *) fc_state->readBuf + fc_pageHeaderSize;
			fc_len = XLOG_BLCKSZ - fc_pageHeaderSize;
			if (fc_pageHeader->xlp_rem_len < fc_len)
				fc_len = fc_pageHeader->xlp_rem_len;

			if (fc_readOff < fc_pageHeaderSize + fc_len)
				fc_readOff = fc_ReadPageInternal(fc_state, fc_targetPagePtr,
										   fc_pageHeaderSize + fc_len);

			memcpy(fc_buffer, (char *) fc_contdata, fc_len);
			fc_buffer += fc_len;
			fc_gotlen += fc_len;

			/* 如果我们刚刚重新组装了记录头，请验证它。 */
			if (!fc_gotheader)
			{
				fc_record = (XLogRecord *) fc_state->readRecordBuf;
				if (!fc_ValidXLogRecordHeader(fc_state, fc_RecPtr, fc_state->DecodeRecPtr,
										   fc_record, fc_randAccess))
					goto err;
				fc_gotheader = true;
			}

			/*
			 * 我们可能需要一个更大的缓冲区。
			 * 我们已经验证了记录头，以防它跨越了页面边界。
			 * 我们还对第二页的 total_len 与 xlp_rem_len 进行了交叉检查，并验证了两个页面的 xlp_pageaddr。
			 */
			if (fc_total_len > fc_state->readRecordBufSize)
			{
				char		fc_save_copy[XLOG_BLCKSZ * 2];

				/*
				 * 保存并恢复我们已经拥有的数据。它不能超过两个页面。
				 */
				Assert(fc_gotlen <= lengthof(fc_save_copy));
				Assert(fc_gotlen <= fc_state->readRecordBufSize);
				memcpy(fc_save_copy, fc_state->readRecordBuf, fc_gotlen);
				fc_allocate_recordbuf(fc_state, fc_total_len);
				memcpy(fc_state->readRecordBuf, fc_save_copy, fc_gotlen);
				fc_buffer = fc_state->readRecordBuf + fc_gotlen;
			}
		} while (fc_gotlen < fc_total_len);
		Assert(fc_gotheader);

		fc_record = (XLogRecord *) fc_state->readRecordBuf;
		if (!fc_ValidXLogRecord(fc_state, fc_record, fc_RecPtr))
			goto err;

		fc_pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) fc_state->readBuf);
		fc_state->DecodeRecPtr = fc_RecPtr;
		fc_state->NextRecPtr = fc_targetPagePtr + fc_pageHeaderSize
			+ MAXALIGN(fc_pageHeader->xlp_rem_len);
	}
	else
	{
		/* 等待记录数据变得可用 */
		fc_readOff = fc_ReadPageInternal(fc_state, fc_targetPagePtr,
								   Min(fc_targetRecOff + fc_total_len, XLOG_BLCKSZ));
		if (fc_readOff == XLREAD_WOULDBLOCK)
			return XLREAD_WOULDBLOCK;
		else if (fc_readOff < 0)
			goto err;

		/* 记录没有跨越页面边界 */
		if (!fc_ValidXLogRecord(fc_state, fc_record, fc_RecPtr))
			goto err;

		fc_state->NextRecPtr = fc_RecPtr + MAXALIGN(fc_total_len);

		fc_state->DecodeRecPtr = fc_RecPtr;
	}

	/*
	 * 如果是 XLOG SWITCH 记录，则进行特殊处理
	 */
	if (fc_record->xl_rmid == RM_XLOG_ID &&
		(fc_record->xl_info & ~XLR_INFO_MASK) == XLOG_SWITCH)
	{
		/* 假装它扩展到段的末尾 */
		fc_state->NextRecPtr += fc_state->segcxt.ws_segsize - 1;
		fc_state->NextRecPtr -= XLogSegmentOffset(fc_state->NextRecPtr, fc_state->segcxt.ws_segsize);
	}

	/*
	 * 如果我们在没有 DecodedXLogRecord 的情况下到达这里，意味着我们需要验证 total_len 
	 * 之前才能信任它，但现在我们已经完成了这一操作。
	 */
	if (fc_decoded == NULL)
	{
		Assert(!fc_nonblocking);
		fc_decoded = fc_XLogReadRecordAlloc(fc_state,
									  fc_total_len,
									  true /* allow_oversized */ );
		/* 分配应始终在 allow_oversized 下进行 */
		Assert(fc_decoded != NULL);
	}

	if (DecodeXLogRecord(fc_state, fc_decoded, fc_record, fc_RecPtr, &fc_errormsg))
	{
		/* 记录下一个记录的位置。 */
		fc_decoded->next_lsn = fc_state->NextRecPtr;

		/*
		 * 如果它在解码缓冲区中，请将解码缓冲区空间标记为
		 * 占用。
		 */
		if (!fc_decoded->oversized)
		{
			/* 新的解码缓冲区头必须经过 MAXALIGN 对齐。 */
			Assert(fc_decoded->size == MAXALIGN(fc_decoded->size));
			if ((char *) fc_decoded == fc_state->decode_buffer)
				fc_state->decode_buffer_tail = fc_state->decode_buffer + fc_decoded->size;
			else
				fc_state->decode_buffer_tail += fc_decoded->size;
		}

		/* 将其插入已解码记录的队列中。 */
		Assert(fc_state->decode_queue_tail != fc_decoded);
		if (fc_state->decode_queue_tail)
			fc_state->decode_queue_tail->next = fc_decoded;
		fc_state->decode_queue_tail = fc_decoded;
		if (!fc_state->decode_queue_head)
			fc_state->decode_queue_head = fc_decoded;
		return XLREAD_SUCCESS;
	}

err:
	if (fc_assembled)
	{
		/*
		 * 当一个跨多个页面的记录需要组装时我们到达这里，但
		 * 有些事情出了错——可能是一个 contrecord 部分丢失了。
		 * 如果调用者是 WAL 重放，它会知道中止的记录在哪里
		 * 和后续 WAL 应该被写入的位置，标记下一个部分为
		 * XLP_FIRST_IS_OVERWRITE_CONTRECORD，这将
		 * 进一步通知下游的 WAL 消费者该损坏的 WAL 记录
		 * 应被忽略。
		 */
		fc_state->abortedRecPtr = fc_RecPtr;
		fc_state->missingContrecPtr = fc_targetPagePtr;

		/*
		 * 如果我们在这里没有报告错误，确保有一个错误被
		 * 排队，以便 XLogPrefetcherReadRecord() 不会再把我们带回
		 * 一次并破坏上述状态。
		 */
		fc_state->errormsg_deferred = true;
	}

	if (fc_decoded && fc_decoded->oversized)
		pfree(fc_decoded);

	/*
	 * 使读取状态失效。我们可能在故障后从不同的来源读取。
	 */
	fc_XLogReaderInvalReadState(fc_state);

	/*
	 * 如果一个错误写入了 errmsg_buf，它将在所有成功解码的记录
	 * 从读取队列后返回给 XLogReadRecord() 的调用者。
	 */

	return XLREAD_FAIL;
}

/*
 * 尝试解码下一个可用的记录，并返回它。该记录还将返回
 * 给 XLogNextRecord()，必须调用该函数以“消费”
 * 每个记录。
 *
 * 如果非阻塞为真，可能由于缺少数据或 WAL 解码
 * 空间返回 NULL。
 */
DecodedXLogRecord *
XLogReadAhead(XLogReaderState *fc_state, bool fc_nonblocking)
{
	XLogPageReadResult fc_result;

	if (fc_state->errormsg_deferred)
		return NULL;

	fc_result = fc_XLogDecodeNextRecord(fc_state, fc_nonblocking);
	if (fc_result == XLREAD_SUCCESS)
	{
		Assert(fc_state->decode_queue_tail != NULL);
		return fc_state->decode_queue_tail;
	}

	return NULL;
}

/*
 * 通过 page_read() 回调读取一个 xlog 页面，包括至少 [pageptr, reqLen] 的有效数据。
 *
 * 如果由于某种原因无法读取所需页面，则返回 XLREAD_FAIL；
 * 在这种情况下，将设置 errormsg_buf（除非错误发生在
 * page_read 回调中）。
 *
 * 如果请求的数据在不等待的情况下无法读取，则返回 XLREAD_WOULDBLOCK。
 * 只有在已安装的 page_read 回调尊重 state->nonblocking 标志的情况下
 * 才能返回，并且无法立即读取请求的数据。
 *
 * 如果我们知道我们有所需的数据，并且自缓存数据以来没有任何错误，
 * 我们将从阅读器本地缓存中获取页面。
 */
static int fc_ReadPageInternal(XLogReaderState *fc_state, XLogRecPtr fc_pageptr, int fc_reqLen)
{
	int			fc_readLen;
	uint32		fc_targetPageOff;
	XLogSegNo	fc_targetSegNo;
	XLogPageHeader fc_hdr;

	Assert((fc_pageptr % XLOG_BLCKSZ) == 0);

	XLByteToSeg(fc_pageptr, fc_targetSegNo, fc_state->segcxt.ws_segsize);
	fc_targetPageOff = XLogSegmentOffset(fc_pageptr, fc_state->segcxt.ws_segsize);

	/* 检查我们是否已经拥有所有请求的数据 */
	if (fc_targetSegNo == fc_state->seg.ws_segno &&
		fc_targetPageOff == fc_state->segoff && fc_reqLen <= fc_state->readLen)
		return fc_state->readLen;

	/*
	 * 在读取尝试之前使内部缓冲区的内容失效。仅将
	 * 长度设置为 0，而不是完全进行 XLogReaderInvalReadState()，
	 * 以便我们不会忘记最后成功读取的段。
	 */
	fc_state->readLen = 0;

	/*
	 * 数据不在我们的缓冲区中。
	 *
	 * 每次我们实际读取段时，即使之前查看过其中部分内容，我们都需要进行验证，因为 page_read 回调现在可能在从不同源重新读取数据。
	 *
	 * 每当切换到新的 WAL 段时，我们都会读取文件的第一页并验证其标头，即使那不是目标记录所在的位置。这是为了检查第一页“长”标头中存在的附加识别信息。
	 */
	if (fc_targetSegNo != fc_state->seg.ws_segno && fc_targetPageOff != 0)
	{
		XLogRecPtr	fc_targetSegmentPtr = fc_pageptr - fc_targetPageOff;

		fc_readLen = fc_state->routine.page_read(fc_state, fc_targetSegmentPtr, XLOG_BLCKSZ,
										   fc_state->currRecPtr,
										   fc_state->readBuf);
		if (fc_readLen == XLREAD_WOULDBLOCK)
			return XLREAD_WOULDBLOCK;
		else if (fc_readLen < 0)
			goto err;

		/* 我们可以确保有足够的 WAL 可用，我们向后滚动 */
		Assert(fc_readLen == XLOG_BLCKSZ);

		if (!XLogReaderValidatePageHeader(fc_state, fc_targetSegmentPtr,
										  fc_state->readBuf))
			goto err;
	}

	/*
	 * 首先，读取请求的数据长度，但至少要有一个短页面标头，以便我们可以验证它。
	 */
	fc_readLen = fc_state->routine.page_read(fc_state, fc_pageptr, Max(fc_reqLen, SizeOfXLogShortPHD),
									   fc_state->currRecPtr,
									   fc_state->readBuf);
	if (fc_readLen == XLREAD_WOULDBLOCK)
		return XLREAD_WOULDBLOCK;
	else if (fc_readLen < 0)
		goto err;

	Assert(fc_readLen <= XLOG_BLCKSZ);

	/* 我们有足够的数据来检查标头长度吗？ */
	if (fc_readLen <= SizeOfXLogShortPHD)
		goto err;

	Assert(fc_readLen >= fc_reqLen);

	fc_hdr = (XLogPageHeader) fc_state->readBuf;

	/* 仍然不够 */
	if (fc_readLen < XLogPageHeaderSize(fc_hdr))
	{
		fc_readLen = fc_state->routine.page_read(fc_state, fc_pageptr, XLogPageHeaderSize(fc_hdr),
										   fc_state->currRecPtr,
										   fc_state->readBuf);
		if (fc_readLen == XLREAD_WOULDBLOCK)
			return XLREAD_WOULDBLOCK;
		else if (fc_readLen < 0)
			goto err;
	}

	/*
	 * 现在我们知道我们有完整的标头，验证它。
	 */
	if (!XLogReaderValidatePageHeader(fc_state, fc_pageptr, (char *) fc_hdr))
		goto err;

	/* 更新读取状态信息 */
	fc_state->seg.ws_segno = fc_targetSegNo;
	fc_state->segoff = fc_targetPageOff;
	fc_state->readLen = fc_readLen;

	return fc_readLen;

err:
	fc_XLogReaderInvalReadState(fc_state);

	return XLREAD_FAIL;
}

/*
 * 使 xlogreader 的读取状态失效，以强制重新读取。
 */
static void fc_XLogReaderInvalReadState(XLogReaderState *fc_state)
{
	fc_state->seg.ws_segno = 0;
	fc_state->segoff = 0;
	fc_state->readLen = 0;
}

/*
 * 验证 XLOG 记录标头。
 *
 * 这只是一个便于避免在 XLogReadRecord 中重复代码的子例程。
 * 它并不打算从其他地方使用。
 */
static bool fc_ValidXLogRecordHeader(XLogReaderState *fc_state, XLogRecPtr fc_RecPtr,
					  XLogRecPtr fc_PrevRecPtr, XLogRecord *fc_record,
					  bool fc_randAccess)
{
	if (fc_record->xl_tot_len < SizeOfXLogRecord)
	{
		fc_report_invalid_record(fc_state,
							  "invalid record length at %X/%X: wanted %u, got %u",
							  LSN_FORMAT_ARGS(fc_RecPtr),
							  (uint32) SizeOfXLogRecord, fc_record->xl_tot_len);
		return false;
	}
	if (!RmgrIdIsValid(fc_record->xl_rmid))
	{
		fc_report_invalid_record(fc_state,
							  "invalid resource manager ID %u at %X/%X",
							  fc_record->xl_rmid, LSN_FORMAT_ARGS(fc_RecPtr));
		return false;
	}
	if (fc_randAccess)
	{
		/*
		 * 我们不能完全验证 prev-link，但它肯定应该小于记录自身的地址。
		 */
		if (!(fc_record->xl_prev < fc_RecPtr))
		{
			fc_report_invalid_record(fc_state,
								  "record with incorrect prev-link %X/%X at %X/%X",
								  LSN_FORMAT_ARGS(fc_record->xl_prev),
								  LSN_FORMAT_ARGS(fc_RecPtr));
			return false;
		}
	}
	else
	{
		/*
		 * 记录的 prev-link 应该与我们之前的位置完全匹配。此检查可防止撕裂的 WAL 页面，其中一个过时但看起来有效的 WAL 记录开始于扇区边界。
		 */
		if (fc_record->xl_prev != fc_PrevRecPtr)
		{
			fc_report_invalid_record(fc_state,
								  "record with incorrect prev-link %X/%X at %X/%X",
								  LSN_FORMAT_ARGS(fc_record->xl_prev),
								  LSN_FORMAT_ARGS(fc_RecPtr));
			return false;
		}
	}

	return true;
}


/*
 * CRC 检查一个 XLOG 记录。我们不相信 XLOG 记录的内容（除了计算要读取的数据量的最小程度）直到我们检查了 CRC。
 *
 * 我们假设记录的所有数据（即 xl_tot_len 字节）都已读入内存中的 *record。此外，ValidXLogRecordHeader() 已接受记录的标头，这意味着特别是 xl_tot_len 至少是 SizeOfXLogRecord。
 */
static bool fc_ValidXLogRecord(XLogReaderState *fc_state, XLogRecord *fc_record, XLogRecPtr fc_recptr)
{
	pg_crc32c	fc_crc;

	Assert(fc_record->xl_tot_len >= SizeOfXLogRecord);

	/* 计算 CRC */
	INIT_CRC32C(fc_crc);
	COMP_CRC32C(fc_crc, ((char *) fc_record) + SizeOfXLogRecord, fc_record->xl_tot_len - SizeOfXLogRecord);
	/* 最后包含记录标头 */
	COMP_CRC32C(fc_crc, (char *) fc_record, offsetof(XLogRecord, xl_crc));
	FIN_CRC32C(fc_crc);

	if (!EQ_CRC32C(fc_record->xl_crc, fc_crc))
	{
		fc_report_invalid_record(fc_state,
							  "incorrect resource manager data checksum in record at %X/%X",
							  LSN_FORMAT_ARGS(fc_recptr));
		return false;
	}

	return true;
}

/*
 * 验证页面标头。
 *
 * 检查 'phdr' 是否有效，作为位于 'recptr' 位置的 XLog 页面标头。
 */
bool XLogReaderValidatePageHeader(XLogReaderState *fc_state, XLogRecPtr fc_recptr,
							 char *fc_phdr)
{
	XLogRecPtr	fc_recaddr;
	XLogSegNo	fc_segno;
	int32		fc_offset;
	XLogPageHeader fc_hdr = (XLogPageHeader) fc_phdr;

	Assert((fc_recptr % XLOG_BLCKSZ) == 0);

	XLByteToSeg(fc_recptr, fc_segno, fc_state->segcxt.ws_segsize);
	fc_offset = XLogSegmentOffset(fc_recptr, fc_state->segcxt.ws_segsize);

	XLogSegNoOffsetToRecPtr(fc_segno, fc_offset, fc_state->segcxt.ws_segsize, fc_recaddr);

	if (fc_hdr->xlp_magic != XLOG_PAGE_MAGIC)
	{
		char		fc_fname[MAXFNAMELEN];

		XLogFileName(fc_fname, fc_state->seg.ws_tli, fc_segno, fc_state->segcxt.ws_segsize);

		fc_report_invalid_record(fc_state,
							  "invalid magic number %04X in log segment %s, offset %u",
							  fc_hdr->xlp_magic,
							  fc_fname,
							  fc_offset);
		return false;
	}

	if ((fc_hdr->xlp_info & ~XLP_ALL_FLAGS) != 0)
	{
		char		fc_fname[MAXFNAMELEN];

		XLogFileName(fc_fname, fc_state->seg.ws_tli, fc_segno, fc_state->segcxt.ws_segsize);

		fc_report_invalid_record(fc_state,
							  "invalid info bits %04X in log segment %s, offset %u",
							  fc_hdr->xlp_info,
							  fc_fname,
							  fc_offset);
		return false;
	}

	if (fc_hdr->xlp_info & XLP_LONG_HEADER)
	{
		XLogLongPageHeader fc_longhdr = (XLogLongPageHeader) fc_hdr;

		if (fc_state->system_identifier &&
			fc_longhdr->xlp_sysid != fc_state->system_identifier)
		{
			fc_report_invalid_record(fc_state,
								  "WAL file is from different database system: WAL file database system identifier is %llu, pg_control database system identifier is %llu",
								  (unsigned long long) fc_longhdr->xlp_sysid,
								  (unsigned long long) fc_state->system_identifier);
			return false;
		}
		else if (fc_longhdr->xlp_seg_size != fc_state->segcxt.ws_segsize)
		{
			fc_report_invalid_record(fc_state,
								  "WAL file is from different database system: incorrect segment size in page header");
			return false;
		}
		else if (fc_longhdr->xlp_xlog_blcksz != XLOG_BLCKSZ)
		{
			fc_report_invalid_record(fc_state,
								  "WAL file is from different database system: incorrect XLOG_BLCKSZ in page header");
			return false;
		}
	}
	else if (fc_offset == 0)
	{
		char		fc_fname[MAXFNAMELEN];

		XLogFileName(fc_fname, fc_state->seg.ws_tli, fc_segno, fc_state->segcxt.ws_segsize);

		/* 嗯，文件的第一页没有长标头？ */
		fc_report_invalid_record(fc_state,
							  "invalid info bits %04X in log segment %s, offset %u",
							  fc_hdr->xlp_info,
							  fc_fname,
							  fc_offset);
		return false;
	}

	/*
	 * 检查页面上的地址是否与我们预期的一致。此检查通常在旧的 WAL 段被回收且尚未被新数据覆盖时失败。
	 */
	if (fc_hdr->xlp_pageaddr != fc_recaddr)
	{
		char		fc_fname[MAXFNAMELEN];

		XLogFileName(fc_fname, fc_state->seg.ws_tli, fc_segno, fc_state->segcxt.ws_segsize);

		fc_report_invalid_record(fc_state,
							  "unexpected pageaddr %X/%X in log segment %s, offset %u",
							  LSN_FORMAT_ARGS(fc_hdr->xlp_pageaddr),
							  fc_fname,
							  fc_offset);
		return false;
	}

	/*
	 * 由于子时间线始终分配比其直接父时间线更大的 TLI，我们在一致的 WAL 序列的连续页面上永远不应该看到 TLI 后退。
	 *
	 * 有时我们会重新读取一个已经（部分）读取的段。因此我们只验证比最后记住的 LSN 更晚的页面的 TLI。
	 */
	if (fc_recptr > fc_state->latestPagePtr)
	{
		if (fc_hdr->xlp_tli < fc_state->latestPageTLI)
		{
			char		fc_fname[MAXFNAMELEN];

			XLogFileName(fc_fname, fc_state->seg.ws_tli, fc_segno, fc_state->segcxt.ws_segsize);

			fc_report_invalid_record(fc_state,
								  "out-of-sequence timeline ID %u (after %u) in log segment %s, offset %u",
								  fc_hdr->xlp_tli,
								  fc_state->latestPageTLI,
								  fc_fname,
								  fc_offset);
			return false;
		}
	}
	fc_state->latestPagePtr = fc_recptr;
	fc_state->latestPageTLI = fc_hdr->xlp_tli;

	return true;
}

/*
 * 忘记 XLogReaderValidatePageHeader() 产生的错误。
 */
void XLogReaderResetError(XLogReaderState *fc_state)
{
	fc_state->errormsg_buf[0] = '\0';
	fc_state->errormsg_deferred = false;
}

/*
 * 查找第一个 lsn >= RecPtr 的记录。
 *
 * 这与 XLogBeginRead() 的不同之处在于 RecPtr 不需要指向一个有效的记录边界。
 * 这对于检查 RecPtr 是否是一个有效的 xlog 地址以进行读取是很有用的，并且在调试时转储记录时
 * 查找某个地址之后的第一个有效地址。
 *
 * 这会定位读取器，就像 XLogBeginRead() 一样，以便下一次调用 XLogReadRecord()
 * 将读取下一个有效记录。
 */
XLogRecPtr XLogFindNextRecord(XLogReaderState *fc_state, XLogRecPtr fc_RecPtr)
{
	XLogRecPtr	fc_tmpRecPtr;
	XLogRecPtr	fc_found = InvalidXLogRecPtr;
	XLogPageHeader fc_header;
	char	   *fc_errormsg;

	Assert(!XLogRecPtrIsInvalid(fc_RecPtr));

	/* 确保 ReadPageInternal() 不能返回 XLREAD_WOULDBLOCK。 */
	fc_state->nonblocking = false;

	/*
	 * 跳过潜在的续数据，记住这可能跨越多个页面
	 */
	fc_tmpRecPtr = fc_RecPtr;
	while (true)
	{
		XLogRecPtr	fc_targetPagePtr;
		int			fc_targetRecOff;
		uint32		fc_pageHeaderSize;
		int			fc_readLen;

		/*
		 * 计算 targetRecOff。它通常应该等于或大于短页面头，
		 * 因为有效记录不能在那之前的任何地方开始，除非调用者明确指定了某个偏移量，
		 * 或者我们正在跳过多页续记录。不过这没关系，因为
		 * ReadPageInternal() 准备处理这一点，并将读取至少短页面头的内容。
		 */
		fc_targetRecOff = fc_tmpRecPtr % XLOG_BLCKSZ;

		/* 滚动回页面边界 */
		fc_targetPagePtr = fc_tmpRecPtr - fc_targetRecOff;

		/* 读取包含记录的页面 */
		fc_readLen = fc_ReadPageInternal(fc_state, fc_targetPagePtr, fc_targetRecOff);
		if (fc_readLen < 0)
			goto err;

		fc_header = (XLogPageHeader) fc_state->readBuf;

		fc_pageHeaderSize = XLogPageHeaderSize(fc_header);

		/* 确保我们有足够的数据用于页面头 */
		fc_readLen = fc_ReadPageInternal(fc_state, fc_targetPagePtr, fc_pageHeaderSize);
		if (fc_readLen < 0)
			goto err;

		/* 跳过潜在的续数据 */
		if (fc_header->xlp_info & XLP_FIRST_IS_CONTRECORD)
		{
			/*
			 * 如果剩余的续数据长度超过了此页面可以容纳的长度，
			 * 则续记录跨越了此页面。读取下一页面并重试。下一页面头中的 xlp_rem_len
			 * 将包含续数据的剩余长度。
			 *
			 * 注意记录头是经过 MAXALIGN 的
			 */
			if (MAXALIGN(fc_header->xlp_rem_len) >= (XLOG_BLCKSZ - fc_pageHeaderSize))
				fc_tmpRecPtr = fc_targetPagePtr + XLOG_BLCKSZ;
			else
			{
				/*
				 * 上一个续记录在此页面结束。设置 tmpRecPtr 指向第一个有效记录
				 */
				fc_tmpRecPtr = fc_targetPagePtr + fc_pageHeaderSize
					+ MAXALIGN(fc_header->xlp_rem_len);
				break;
			}
		}
		else
		{
			fc_tmpRecPtr = fc_targetPagePtr + fc_pageHeaderSize;
			break;
		}
	}

	/*
	 * 我们现在知道 tmpRecPtr 是一个指向有效 XLogRecord 的地址，
	 * 因为我们要么在页面开始后的第一个记录，要么刚刚跳过了续数据的其余部分。
	 */
	XLogBeginRead(fc_state, fc_tmpRecPtr);
	while (XLogReadRecord(fc_state, &fc_errormsg) != NULL)
	{
		/* 超过我们找到的记录，跳出 */
		if (fc_RecPtr <= fc_state->ReadRecPtr)
		{
			/* 将读取器倒回到最后一个记录的开始。 */
			fc_found = fc_state->ReadRecPtr;
			XLogBeginRead(fc_state, fc_found);
			return fc_found;
		}
	}

err:
	fc_XLogReaderInvalReadState(fc_state);

	return InvalidXLogRecPtr;
}

/*
 * 辅助函数以简化 XLogRoutine->page_read 回调的编写。
 * 如果使用此函数，调用者必须在 'state' 中提供 segment_open 回调，因为在这里使用了它。
 *
 * 从时间线 'tli' 中获取 WAL，将 'count' 字节读取到 'buf' 中，
 * 从位置 'startptr' 开始。
 *
 * 如果成功返回 true，发生错误时返回 false，此时 'errinfo' 接收错误细节。
 *
 * XXX 可能应该改进为在可能的情况下直接从 WAL 缓冲区中读取数据。
 */
bool WALRead(XLogReaderState *fc_state,
		char *fc_buf, XLogRecPtr fc_startptr, Size fc_count, TimeLineID fc_tli,
		WALReadError *fc_errinfo)
{
	char	   *fc_p;
	XLogRecPtr	fc_recptr;
	Size		fc_nbytes;

	fc_p = fc_buf;
	fc_recptr = fc_startptr;
	fc_nbytes = fc_count;

	while (fc_nbytes > 0)
	{
		uint32		fc_startoff;
		int			fc_segbytes;
		int			fc_readbytes;

		fc_startoff = XLogSegmentOffset(fc_recptr, fc_state->segcxt.ws_segsize);

		/*
		 * 如果我们想要的数据不在我们已经打开的段中，则关闭我们已经打开的段（如果有的话），
		 * 然后使用调用者提供的 openSegment 回调打开下一个段。
		 */
		if (fc_state->seg.ws_file < 0 ||
			!XLByteInSeg(fc_recptr, fc_state->seg.ws_segno, fc_state->segcxt.ws_segsize) ||
			fc_tli != fc_state->seg.ws_tli)
		{
			XLogSegNo	fc_nextSegNo;

			if (fc_state->seg.ws_file >= 0)
				fc_state->routine.segment_close(fc_state);

			XLByteToSeg(fc_recptr, fc_nextSegNo, fc_state->segcxt.ws_segsize);
			fc_state->routine.segment_open(fc_state, fc_nextSegNo, &fc_tli);

			/* 这不应该发生 -- 表示 segment_open 中存在错误 */
			Assert(fc_state->seg.ws_file >= 0);

			/* 更新当前段信息。 */
			fc_state->seg.ws_tli = fc_tli;
			fc_state->seg.ws_segno = fc_nextSegNo;
		}

		/* 这个段落中有多少字节？ */
		if (fc_nbytes > (fc_state->segcxt.ws_segsize - fc_startoff))
			fc_segbytes = fc_state->segcxt.ws_segsize - fc_startoff;
		else
			fc_segbytes = fc_nbytes;

#ifndef FRONTEND
		pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
#endif

		/* 首先重置 errno；简化报告不影响 errno 的错误 */
		errno = 0;
		fc_readbytes = pg_pread(fc_state->seg.ws_file, fc_p, fc_segbytes, (off_t) fc_startoff);

#ifndef FRONTEND
		pgstat_report_wait_end();
#endif

		if (fc_readbytes <= 0)
		{
			fc_errinfo->wre_errno = errno;
			fc_errinfo->wre_req = fc_segbytes;
			fc_errinfo->wre_read = fc_readbytes;
			fc_errinfo->wre_off = fc_startoff;
			fc_errinfo->wre_seg = fc_state->seg;
			return false;
		}

		/* 更新读取状态 */
		fc_recptr += fc_readbytes;
		fc_nbytes -= fc_readbytes;
		fc_p += fc_readbytes;
	}

	return true;
}

/* ----------------------------------------
 * 解码记录中的数据和块引用的函数。
 * ----------------------------------------
 */

/*
 * 私有函数，用于重置状态，如果我们被要求移动到新的读取位置，则忘记所有已解码的记录。
 */
static void fc_ResetDecoder(XLogReaderState *fc_state)
{
	DecodedXLogRecord *fc_r;

	/* 重置解码记录队列，释放任何超大记录。 */
	while ((fc_r = fc_state->decode_queue_head) != NULL)
	{
		fc_state->decode_queue_head = fc_r->next;
		if (fc_r->oversized)
			pfree(fc_r);
	}
	fc_state->decode_queue_tail = NULL;
	fc_state->decode_queue_head = NULL;
	fc_state->record = NULL;

	/* 重置解码缓冲区为空。 */
	fc_state->decode_buffer_tail = fc_state->decode_buffer;
	fc_state->decode_buffer_head = fc_state->decode_buffer;

	/* 清除错误状态。 */
	fc_state->errormsg_buf[0] = '\0';
	fc_state->errormsg_deferred = false;
}

/*
 * 计算解码记录所需的最大填充量，以记录头中的 xl_tot_len 为准。 这是
 * 解码一个记录所需的输出缓冲区空间，尽管我们可能不会完全使用它。
 *
 * 这个计算是悲观的，假设可能的最大块数，因为缺乏更好的信息。
 */
size_t DecodeXLogRecordRequiredSpace(size_t fc_xl_tot_len)
{
	size_t		fc_size = 0;

	/* 考虑已解码记录结构的固定大小部分。 */
	fc_size += offsetof(DecodedXLogRecord, blocks[0]);
	/* 考虑最大可能大小的灵活块数组。 */
	fc_size += sizeof(DecodedBkpBlock) * (XLR_MAX_BLOCK_ID + 1);
	/* 考虑所有原始主数据和块数据。 */
	fc_size += fc_xl_tot_len;
	/* 我们可能在 main_data 前插入填充。 */
	fc_size += (MAXIMUM_ALIGNOF - 1);
	/* 我们可能在每个块的数据前插入填充。 */
	fc_size += (MAXIMUM_ALIGNOF - 1) * (XLR_MAX_BLOCK_ID + 1);
	/* 我们可能在末尾插入填充。 */
	fc_size += (MAXIMUM_ALIGNOF - 1);

	return fc_size;
}

/*
 * 解码记录。“decoded”必须指向一个对齐的内存区域，该区域至少具有
 * DecodeXLogRecordRequiredSpace(record) 字节的空间。成功时，
 * decoded->size 包含解码记录实际占用的空间，这可能更少。
 *
 * 只有 decoded->oversized 成员必须已经初始化，并且不会被修改。
 * 其他成员将根据需要进行初始化。
 *
 * 出错时，将在 *errormsg 中返回一条人可读的错误消息，
 * 返回值为 false。
 */
bool DecodeXLogRecord(XLogReaderState *fc_state,
				 DecodedXLogRecord *fc_decoded,
				 XLogRecord *fc_record,
				 XLogRecPtr fc_lsn,
				 char **fc_errormsg)
{
	/*
	 * 从记录缓冲区读取下一 _size 字节，但首先检查是否溢出。
	 */
#define COPY_HEADER_FIELD(_dst, _size)			\
	do {										\
		if (fc_remaining < _size)					\
			goto shortdata_err;					\
		memcpy(_dst, fc_ptr, _size);				\
		fc_ptr += _size;							\
		fc_remaining -= _size;						\
	} while(0)

	char	   *fc_ptr;
	char	   *fc_out;
	uint32		fc_remaining;
	uint32		fc_datatotal;
	RelFileNode *fc_rnode = NULL;
	uint8		fc_block_id;

	fc_decoded->header = *fc_record;
	fc_decoded->lsn = fc_lsn;
	fc_decoded->next = NULL;
	fc_decoded->record_origin = InvalidRepOriginId;
	fc_decoded->toplevel_xid = InvalidTransactionId;
	fc_decoded->main_data = NULL;
	fc_decoded->main_data_len = 0;
	fc_decoded->max_block_id = -1;
	fc_ptr = (char *) fc_record;
	fc_ptr += SizeOfXLogRecord;
	fc_remaining = fc_record->xl_tot_len - SizeOfXLogRecord;

	/* 解码头部 */
	fc_datatotal = 0;
	while (fc_remaining > fc_datatotal)
	{
		COPY_HEADER_FIELD(&fc_block_id, sizeof(uint8));

		if (fc_block_id == XLR_BLOCK_ID_DATA_SHORT)
		{
			/* XLogRecordDataHeaderShort */
			uint8		fc_main_data_len;

			COPY_HEADER_FIELD(&fc_main_data_len, sizeof(uint8));

			fc_decoded->main_data_len = fc_main_data_len;
			fc_datatotal += fc_main_data_len;
			break;				/* 根据约定，主数据片段总是最后的 */
		}
		else if (fc_block_id == XLR_BLOCK_ID_DATA_LONG)
		{
			/* XLogRecordDataHeaderLong */
			uint32		fc_main_data_len;

			COPY_HEADER_FIELD(&fc_main_data_len, sizeof(uint32));
			fc_decoded->main_data_len = fc_main_data_len;
			fc_datatotal += fc_main_data_len;
			break;				/* 根据约定，主数据片段总是最后的 */
		}
		else if (fc_block_id == XLR_BLOCK_ID_ORIGIN)
		{
			COPY_HEADER_FIELD(&fc_decoded->record_origin, sizeof(RepOriginId));
		}
		else if (fc_block_id == XLR_BLOCK_ID_TOPLEVEL_XID)
		{
			COPY_HEADER_FIELD(&fc_decoded->toplevel_xid, sizeof(TransactionId));
		}
		else if (fc_block_id <= XLR_MAX_BLOCK_ID)
		{
			/* XLogRecordBlockHeader */
			DecodedBkpBlock *fc_blk;
			uint8		fc_fork_flags;

			/* 将任何中介块 ID 标记为未使用 */
			for (int fc_i = fc_decoded->max_block_id + 1; fc_i < fc_block_id; ++fc_i)
				fc_decoded->blocks[fc_i].in_use = false;

			if (fc_block_id <= fc_decoded->max_block_id)
			{
				fc_report_invalid_record(fc_state,
									  "out-of-order block_id %u at %X/%X",
									  fc_block_id,
									  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
				goto err;
			}
			fc_decoded->max_block_id = fc_block_id;

			fc_blk = &fc_decoded->blocks[fc_block_id];
			fc_blk->in_use = true;
			fc_blk->apply_image = false;

			COPY_HEADER_FIELD(&fc_fork_flags, sizeof(uint8));
			fc_blk->forknum = fc_fork_flags & BKPBLOCK_FORK_MASK;
			fc_blk->flags = fc_fork_flags;
			fc_blk->has_image = ((fc_fork_flags & BKPBLOCK_HAS_IMAGE) != 0);
			fc_blk->has_data = ((fc_fork_flags & BKPBLOCK_HAS_DATA) != 0);

			fc_blk->prefetch_buffer = InvalidBuffer;

			COPY_HEADER_FIELD(&fc_blk->data_len, sizeof(uint16));
			/* 交叉检查 HAS_DATA 标志仅在 data_length > 0 时设置 */
			if (fc_blk->has_data && fc_blk->data_len == 0)
			{
				fc_report_invalid_record(fc_state,
									  "BKPBLOCK_HAS_DATA set, but no data included at %X/%X",
									  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
				goto err;
			}
			if (!fc_blk->has_data && fc_blk->data_len != 0)
			{
				fc_report_invalid_record(fc_state,
									  "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X",
									  (unsigned int) fc_blk->data_len,
									  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
				goto err;
			}
			fc_datatotal += fc_blk->data_len;

			if (fc_blk->has_image)
			{
				COPY_HEADER_FIELD(&fc_blk->bimg_len, sizeof(uint16));
				COPY_HEADER_FIELD(&fc_blk->hole_offset, sizeof(uint16));
				COPY_HEADER_FIELD(&fc_blk->bimg_info, sizeof(uint8));

				fc_blk->apply_image = ((fc_blk->bimg_info & BKPIMAGE_APPLY) != 0);

				if (BKPIMAGE_COMPRESSED(fc_blk->bimg_info))
				{
					if (fc_blk->bimg_info & BKPIMAGE_HAS_HOLE)
						COPY_HEADER_FIELD(&fc_blk->hole_length, sizeof(uint16));
					else
						fc_blk->hole_length = 0;
				}
				else
					fc_blk->hole_length = BLCKSZ - fc_blk->bimg_len;
				fc_datatotal += fc_blk->bimg_len;

				/*
				 * 交叉检查如果设置了 HAS_HOLE 标志，则 hole_offset > 0，hole_length > 0 且
				 * bimg_len < BLCKSZ。
				 */
				if ((fc_blk->bimg_info & BKPIMAGE_HAS_HOLE) &&
					(fc_blk->hole_offset == 0 ||
					 fc_blk->hole_length == 0 ||
					 fc_blk->bimg_len == BLCKSZ))
				{
					fc_report_invalid_record(fc_state,
										  "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at %X/%X",
										  (unsigned int) fc_blk->hole_offset,
										  (unsigned int) fc_blk->hole_length,
										  (unsigned int) fc_blk->bimg_len,
										  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
					goto err;
				}

				/*
				 * 如果没有设置 HAS_HOLE 标志，则交叉检查 hole_offset == 0 和 hole_length == 0。
				 */
				if (!(fc_blk->bimg_info & BKPIMAGE_HAS_HOLE) &&
					(fc_blk->hole_offset != 0 || fc_blk->hole_length != 0))
				{
					fc_report_invalid_record(fc_state,
										  "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X",
										  (unsigned int) fc_blk->hole_offset,
										  (unsigned int) fc_blk->hole_length,
										  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
					goto err;
				}

				/*
				 * 如果 bimg_len 被压缩，则交叉检查 bimg_len < BLCKSZ。
				 */
				if (BKPIMAGE_COMPRESSED(fc_blk->bimg_info) &&
					fc_blk->bimg_len == BLCKSZ)
				{
					fc_report_invalid_record(fc_state,
										  "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X",
										  (unsigned int) fc_blk->bimg_len,
										  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
					goto err;
				}

				/*
				 * 如果既没有设置 HAS_HOLE 标志也没有 COMPRESSED()，则交叉检查 bimg_len = BLCKSZ。
				 */
				if (!(fc_blk->bimg_info & BKPIMAGE_HAS_HOLE) &&
					!BKPIMAGE_COMPRESSED(fc_blk->bimg_info) &&
					fc_blk->bimg_len != BLCKSZ)
				{
					fc_report_invalid_record(fc_state,
										  "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image length is %u at %X/%X",
										  (unsigned int) fc_blk->data_len,
										  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
					goto err;
				}
			}
			if (!(fc_fork_flags & BKPBLOCK_SAME_REL))
			{
				COPY_HEADER_FIELD(&fc_blk->rnode, sizeof(RelFileNode));
				fc_rnode = &fc_blk->rnode;
			}
			else
			{
				if (fc_rnode == NULL)
				{
					fc_report_invalid_record(fc_state,
										  "BKPBLOCK_SAME_REL set but no previous rel at %X/%X",
										  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
					goto err;
				}

				fc_blk->rnode = *fc_rnode;
			}
			COPY_HEADER_FIELD(&fc_blk->blkno, sizeof(BlockNumber));
		}
		else
		{
			fc_report_invalid_record(fc_state,
								  "invalid block_id %u at %X/%X",
								  fc_block_id, LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
			goto err;
		}
	}

	if (fc_remaining != fc_datatotal)
		goto shortdata_err;

	/*
	 * 好的，我们已经解析了片段头，验证了
	 * 片段中有效载荷的总长度等于剩余的数据量。
	 * 将每个片段的数据复制到块数组之后的连续空间中，
	 * 在数据片段之前插入对齐填充，以便它们
	 * 可以被 REDO 例程转换为结构指针。
	 */
	fc_out = ((char *) fc_decoded) +
		offsetof(DecodedXLogRecord, blocks) +
		sizeof(fc_decoded->blocks[0]) * (fc_decoded->max_block_id + 1);

	/* 块数据优先 */
	for (fc_block_id = 0; fc_block_id <= fc_decoded->max_block_id; fc_block_id++)
	{
		DecodedBkpBlock *fc_blk = &fc_decoded->blocks[fc_block_id];

		if (!fc_blk->in_use)
			continue;

		Assert(fc_blk->has_image || !fc_blk->apply_image);

		if (fc_blk->has_image)
		{
			/* 不需要对齐图像 */
			fc_blk->bkp_image = fc_out;
			memcpy(fc_out, fc_ptr, fc_blk->bimg_len);
			fc_ptr += fc_blk->bimg_len;
			fc_out += fc_blk->bimg_len;
		}
		if (fc_blk->has_data)
		{
			fc_out = (char *) MAXALIGN(fc_out);
			fc_blk->data = fc_out;
			memcpy(fc_blk->data, fc_ptr, fc_blk->data_len);
			fc_ptr += fc_blk->data_len;
			fc_out += fc_blk->data_len;
		}
	}

	/* 最后是主数据 */
	if (fc_decoded->main_data_len > 0)
	{
		fc_out = (char *) MAXALIGN(fc_out);
		fc_decoded->main_data = fc_out;
		memcpy(fc_decoded->main_data, fc_ptr, fc_decoded->main_data_len);
		fc_ptr += fc_decoded->main_data_len;
		fc_out += fc_decoded->main_data_len;
	}

	/* 报告我们使用的实际大小。 */
	fc_decoded->size = MAXALIGN(fc_out - (char *) fc_decoded);
	Assert(DecodeXLogRecordRequiredSpace(fc_record->xl_tot_len) >=
		   fc_decoded->size);

	return true;

shortdata_err:
	fc_report_invalid_record(fc_state,
						  "record with invalid length at %X/%X",
						  LSN_FORMAT_ARGS(fc_state->ReadRecPtr));
err:
	*fc_errormsg = fc_state->errormsg_buf;

	return false;
}

/*
 * 返回关于块引用所引用块的信息。
 *
 * 这类似于 XLogRecGetBlockTagExtended，除了块引用
 * 必须存在，并且无法访问 prefetch_buffer。
 */
void XLogRecGetBlockTag(XLogReaderState *fc_record, uint8 fc_block_id,
				   RelFileNode *fc_rnode, ForkNumber *fc_forknum, BlockNumber *fc_blknum)
{
	if (!XLogRecGetBlockTagExtended(fc_record, fc_block_id, fc_rnode, fc_forknum, fc_blknum,
									NULL))
	{
#ifndef FRONTEND
		elog(ERROR, "could not locate backup block with ID %d in WAL record",
			 fc_block_id);
#else
		pg_fatal("could not locate backup block with ID %d in WAL record",
				 fc_block_id);
#endif
	}
}

/*
 * 返回关于块引用所引用块的信息，
 * 可选地包括块可能已经在的缓冲区。
 *
 * 如果 WAL 记录包含具有给定 ID 的块引用，*rnode，
 * *forknum，*blknum 和 *prefetch_buffer（如果不为 NULL）将被填充，并
 * 返回 true。否则返回 false。
 */
bool XLogRecGetBlockTagExtended(XLogReaderState *fc_record, uint8 fc_block_id,
						   RelFileNode *fc_rnode, ForkNumber *fc_forknum,
						   BlockNumber *fc_blknum,
						   Buffer *fc_prefetch_buffer)
{
	DecodedBkpBlock *fc_bkpb;

	if (!XLogRecHasBlockRef(fc_record, fc_block_id))
		return false;

	fc_bkpb = &fc_record->record->blocks[fc_block_id];
	if (fc_rnode)
		*fc_rnode = fc_bkpb->rnode;
	if (fc_forknum)
		*fc_forknum = fc_bkpb->forknum;
	if (fc_blknum)
		*fc_blknum = fc_bkpb->blkno;
	if (fc_prefetch_buffer)
		*fc_prefetch_buffer = fc_bkpb->prefetch_buffer;
	return true;
}

/*
 * 返回与块引用相关联的数据，如果没有数据则返回 NULL
 * （例如，因为采用了全页图像）。返回的指针指向一个 MAXALIGN 的缓冲区。
 */
char * XLogRecGetBlockData(XLogReaderState *fc_record, uint8 fc_block_id, Size *fc_len)
{
	DecodedBkpBlock *fc_bkpb;

	if (fc_block_id > fc_record->record->max_block_id ||
		!fc_record->record->blocks[fc_block_id].in_use)
		return NULL;

	fc_bkpb = &fc_record->record->blocks[fc_block_id];

	if (!fc_bkpb->has_data)
	{
		if (fc_len)
			*fc_len = 0;
		return NULL;
	}
	else
	{
		if (fc_len)
			*fc_len = fc_bkpb->data_len;
		return fc_bkpb->data;
	}
}

/*
 * 从附加到 XLOG 记录的备份块中恢复全页图像。
 *
 * 如果恢复了全页图像则返回 true，失败时返回 false
 * 并带有错误以供调用者处理。
 */
bool RestoreBlockImage(XLogReaderState *fc_record, uint8 fc_block_id, char *fc_page)
{
	DecodedBkpBlock *fc_bkpb;
	char	   *fc_ptr;
	PGAlignedBlock fc_tmp;

	if (fc_block_id > fc_record->record->max_block_id ||
		!fc_record->record->blocks[fc_block_id].in_use)
	{
		fc_report_invalid_record(fc_record,
							  "could not restore image at %X/%X with invalid block %d specified",
							  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
							  fc_block_id);
		return false;
	}
	if (!fc_record->record->blocks[fc_block_id].has_image)
	{
		fc_report_invalid_record(fc_record, "could not restore image at %X/%X with invalid state, block %d",
							  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
							  fc_block_id);
		return false;
	}

	fc_bkpb = &fc_record->record->blocks[fc_block_id];
	fc_ptr = fc_bkpb->bkp_image;

	if (BKPIMAGE_COMPRESSED(fc_bkpb->bimg_info))
	{
		/* 如果备份块图像被压缩，解压缩它 */
		bool		fc_decomp_success = true;

		if ((fc_bkpb->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
		{
			if (pglz_decompress(fc_ptr, fc_bkpb->bimg_len, fc_tmp.data,
								BLCKSZ - fc_bkpb->hole_length, true) < 0)
				fc_decomp_success = false;
		}
		else if ((fc_bkpb->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
		{
#ifdef USE_LZ4
			if (LZ4_decompress_safe(fc_ptr, fc_tmp.data,
									fc_bkpb->bimg_len, BLCKSZ - fc_bkpb->hole_length) <= 0)
				fc_decomp_success = false;
#else
			fc_report_invalid_record(fc_record, "could not restore image at %X/%X compressed with %s not supported by build, block %d",
								  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
								  "LZ4",
								  fc_block_id);
			return false;
#endif
		}
		else if ((fc_bkpb->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
		{
#ifdef USE_ZSTD
			size_t		fc_decomp_result = ZSTD_decompress(fc_tmp.data,
														BLCKSZ - fc_bkpb->hole_length,
														fc_ptr, fc_bkpb->bimg_len);

			if (ZSTD_isError(fc_decomp_result))
				fc_decomp_success = false;
#else
			fc_report_invalid_record(fc_record, "could not restore image at %X/%X compressed with %s not supported by build, block %d",
								  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
								  "zstd",
								  fc_block_id);
			return false;
#endif
		}
		else
		{
			fc_report_invalid_record(fc_record, "could not restore image at %X/%X compressed with unknown method, block %d",
								  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
								  fc_block_id);
			return false;
		}

		if (!fc_decomp_success)
		{
			fc_report_invalid_record(fc_record, "could not decompress image at %X/%X, block %d",
								  LSN_FORMAT_ARGS(fc_record->ReadRecPtr),
								  fc_block_id);
			return false;
		}

		fc_ptr = fc_tmp.data;
	}

	/* 生成页面，如有必要考虑空洞 */
	if (fc_bkpb->hole_length == 0)
	{
		memcpy(fc_page, fc_ptr, BLCKSZ);
	}
	else
	{
		memcpy(fc_page, fc_ptr, fc_bkpb->hole_offset);
		/* 必须对空洞进行零填充 */
		MemSet(fc_page + fc_bkpb->hole_offset, 0, fc_bkpb->hole_length);
		memcpy(fc_page + (fc_bkpb->hole_offset + fc_bkpb->hole_length),
			   fc_ptr + fc_bkpb->hole_offset,
			   BLCKSZ - (fc_bkpb->hole_offset + fc_bkpb->hole_length));
	}

	return true;
}

#ifndef FRONTEND

/*
 * 从 WAL 记录中提取 FullTransactionId。
 */
FullTransactionId XLogRecGetFullXid(XLogReaderState *fc_record)
{
	TransactionId fc_xid,
				fc_next_xid;
	uint32		fc_epoch;

	/*
	 * 此函数仅在重放期间安全，因为它依赖于
	 * 重放状态。有关更多信息，请参见 AdvanceNextFullTransactionIdPastXid()。
	 */
	Assert(AmStartupProcess() || !IsUnderPostmaster);

	fc_xid = XLogRecGetXid(fc_record);
	fc_next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
	fc_epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);

	/*
	 * 如果 xid 在数值上大于 next_xid，那么它必须来自最后一个
	 * 纪元。
	 */
	if (unlikely(fc_xid > fc_next_xid))
		--fc_epoch;

	return FullTransactionIdFromEpochAndXid(fc_epoch, fc_xid);
}

#endif
